using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using needle.Services.astra.Collections;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// List which can notify of items and their property changes.
	/// Items cannot be default(<typeparamref name="T"/>).
	/// </summary>
	/// <typeparam name="T">Item type.</typeparam>
	public class ItemsPropertyChangeList<T> : ItemsChangeList<T>, INotifyPropertyChanged
		where T: INotifyPropertyChanged
	{
		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="list">
		/// Generic list to be wrapped - its direct changes are not notified by <see cref="ItemsChange"/>.
		/// If null, new <see cref="List{T}"/> is created and used.
		/// </param>
		/// <param name="itemValidator">See <see cref="ItemValidator"/>.</param>
		/// <exception cref="ArgumentException">An item in <paramref name="list"/> is invalid.</exception>
		public ItemsPropertyChangeList(IList<T> list, Predicate<T> itemValidator)
			: base(list, itemValidator)
		{
			if (list == null)
				return;
			foreach (T item in list)
			{
				if (item != null)
					item.PropertyChanged += Item_PropertyChanged;
			}
		}
		/// <summary>
		/// Constructor.
		/// <see cref="ItemValidator"/> is null.
		/// </summary>
		/// <param name="list">
		/// Generic list to be wrapped - its direct changes are not notified by <see cref="ItemsChange"/>.
		/// If null, new <see cref="List{T}"/> is created and used.
		/// </param>
		public ItemsPropertyChangeList(IList<T> list)
			: this(list, null)
		{
		}

		#endregion

		/// <summary>
		/// Called when an item fires <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
		/// </summary>
		/// <remarks>This implementation calls <see cref="OnPropertyChanged(object, PropertyChangedEventArgs)"/> with parameters from the item event.</remarks>
		/// <param name="sender">The item.</param>
		/// <param name="e">The item`s event arguments.</param>
		protected virtual void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			OnPropertyChanged(sender, e);
		}
		/// <summary>
		/// Calls base and adds <see cref="Item_PropertyChanged"/> handler for <paramref name="item"/>.
		/// </summary>
		protected override void InsertItem(int index, T item)
		{
			base.InsertItem(index, item);
			item.PropertyChanged += Item_PropertyChanged;
		}
		/// <summary>
		/// Calls base and removes <see cref="Item_PropertyChanged"/> handler for <paramref name="item"/>.
		/// </summary>
		protected override void RemoveItem(int index)
		{
			T item = this[index];
			item.PropertyChanged -= Item_PropertyChanged;
			base.RemoveItem(index);
		}
		/// <summary>
		/// Removes <see cref="Item_PropertyChanged"/> handler for all items and calls base.
		/// </summary>
		protected override void ClearItems()
		{
			foreach (T item in this)
			{
				item.PropertyChanged -= Item_PropertyChanged;
			}
			base.ClearItems();
		}
		/// <summary>
		/// Calls base and removes/adds <see cref="Item_PropertyChanged"/> handler for oldItem/<paramref name="item"/>.
		/// </summary>
		protected override void SetItem(int index, T item)
		{
			T oldItem = this[index];
			oldItem.PropertyChanged -= Item_PropertyChanged;
			base.SetItem(index, item);
			item.PropertyChanged += Item_PropertyChanged;
		}

		/// <summary>
		/// Calls <see cref="OnItemPropertyChanged"/>.
		/// </summary>
		private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			OnItemPropertyChanged(sender, e);
		}

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Fired when an item`s property is changed.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Fires <see cref="PropertyChanged"/> event.
		/// </summary>
		/// <param name="propertyName">Name of changed property.</param>
		protected void OnPropertyChanged(string propertyName)
		{
			OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
		/// <summary>
		/// Fires <see cref="PropertyChanged"/> event.
		/// </summary>
		/// <param name="sender">Event sender.</param>
		/// <param name="e">Event arguments.</param>
		protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (this.PropertyChanged != null)
				this.PropertyChanged(sender, e);
		}

		#endregion
	}
}
